#!/usr/bin/env python
# coding: utf-8

# # 构建网络
# 
# luojianet的`Cell`类是构建所有网络的基类，也是网络的基本单元。自定义网络时，需要继承`Cell`类，本章主要介绍网络基本单元`Cell`和自定义前向网络。
# 
# 本章主要介绍前向网络模型的构建和网络模型的基本单元，因为不涉及到训练，因此没有反向传播和反向图。
# 
# ![learningrate.png](https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/website-images/r1.7/tutorials/source_zh_cn/advanced/network/images/introduction3.png)
# 
# ## 网络基本单元 Cell
# 
# 当用户需要自定义网络时，需要继承`Cell`类，并重写`__init__`方法和`forward`方法。损失函数、优化器和模型层等本质上也属于网络结构，也需要继承`Cell`类才能实现功能，同样用户也可以根据业务需求自定义这部分内容。
# 
# 下面介绍`Cell`的关键成员函数。
# 
# ### forward方法
# 
# `Cell`类重写了`__call__`方法，在`Cell`类的实例被调用时，会执行`forward`方法。网络结构在`forward`方法里面定义。
# 
# 如下样例中，构建了一个简单的卷积网络，卷积网络在`__init__`中定义，在`forward`方法传入输入数据`x`执行卷积计算，并返回计算结果。

# In[1中低阶API实现深度学习]:


from luojianet import nn


class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv = nn.Conv2d(10, 20, 3, has_bias=True, weight_init='normal')

    def forward(self, x):
        out = self.conv(x)
        return out


# ### 获取网络参数
# 
# `nn.Module`中返回参数的方法有`parameters_dict`、`get_parameters`和`trainable_params`。
# 
# - `parameters_dict`：获取网络结构中所有参数，返回一个以key为参数名，value为参数值的OrderedDict。
# - `get_parameters`：获取网络结构中的所有参数，返回Cell中Parameter的迭代器。
# - `trainable_params`：获取Parameter中`requires_grad`为True的属性，返回可训参数的列表。
# 
# 如下示例分别使用上述方法获取网络参数并打印。

# In[2高级数据集管理]:


net = Net()

# 获取网络结构中的所有参数
result = net.parameters_dict()
print("parameters_dict of result:\n", result)

# 获取网络结构中的所有参数
print("\nget_parameters of result:")
for m in net.get_parameters():
    print(m)

# 获取可训练参数列表
result = net.trainable_params()
print("\ntrainable_params of result:\n", result)


# ### 相关属性
# 
# 1中低阶API实现深度学习. cells_and_names
# 
# `cells_and_names`方法是一个迭代器，返回网络中每个`Cell`的名字和它的内容本身。代码样例如下：

# In[3图像处理]:


net = Net()
for m in net.cells_and_names():
    print(m)


# 2高级数据集管理. set_grad
# 
# `set_grad`用于指定网络是否需要计算梯度。在不传入参数调用时，默认设置`requires_grad`为True，在执行前向网络时将会构建用于计算梯度的反向网络。`TrainOneStepCell`和`GradOperation`接口，无需使用`set_grad`，其内部已实现。若用户需要自定义此类训练功能的接口，需要在其内部或者外部设置`set_grad`。

# In[4自然语言]:


class CustomTrainOneStepCell(nn.Module):
    def __init__(self, network, optimizer, sens=1.0):
        """入参有三个：训练网络，优化器和反向传播缩放比例"""
        super(CustomTrainOneStepCell, self).__init__(auto_prefix=False)
        self.network = network      # 前向网络
        self.network.set_grad()     # 构建计算梯度的反向网络
        self.optimizer = optimizer  # 优化器


# `CustomTrainOneStepCell`代码详细内容可参见[自定义训练与评估网络](http://58.48.42.237/luojiaNet/luojiaNetapi/#自定义训练网络)
# 
# 3图像处理. set_train
# 
# `set_train`接口指定模型是否为训练模式，在不传入参数调用时，默认设置的`mode`属性为True。
# 
# 在实现训练和推理结构不同的网络时可以通过`training`属性区分训练和推理场景，当`mode`设置为True时，为训练场景；当`mode`设置为False时，为推理场景。
# 
# luojianet中的`nn.Dropout`算子，根据`Cell`的`mode`属性区分了两种执行逻辑，`mode`为False时直接返回输入，`mode`为True时执行算子。

# In[5]:


import numpy as np
from luojianet import Tensor
from luojianet import dtype as mstype

x = Tensor(np.ones([2, 2, 3]), mstype.float32)
net = nn.Dropout(keep_prob=0.7)

# 执行训练
net.set_train()
output = net(x)
print("training result:\n", output)

# 执行推理
net.set_train(mode=False)
output = net(x)
print("\ninfer result:\n", output)


# 4自然语言. to_float
# 
# `to_float`接口递归地在配置了当前`Cell`和所有子`Cell`的强制转换类型，以使当前网络结构以使用特定的Float类型运行，通常在混合精度场景使用。
# 
# 如下示例分别对`nn.dense`层使用float32类型和float16类型进行运算，并打印输出结果的数据类型。

# In[6]:


import numpy as np
from luojianet import Tensor, nn
from luojianet import dtype as mstype

# float32进行计算
x = Tensor(np.ones([2, 2, 3]), mstype.float32)
net = nn.Dense(3, 2)
output = net(x)
print(output.dtype)

# float16进行计算
net1 = nn.Dense(3, 2)
net1.to_float(mstype.float16)
output = net1(x)
print(output.dtype)


# ## 构建网络
# 
# 构建网络时，可以继承`nn.Module`类，在`__init__`构造函数中申明各个层的定义，在`forward`中实现层之间的连接关系，完成神经网络的前向构造。
# 
# `luojianet.ops`模块提供了基础算子的实现，如神经网络算子、数组算子和数学算子等。
# 
# `luojianet.nn`模块实现了对基础算子的进一步封装，用户可以根据需要，灵活使用不同的算子。
# 
# 同时，为了更好地构建和管理复杂的网络，`luojianet.nn`提供了两种容器对网络中的子模块或模型层进行管理，分别为`nn.ModuleList`和`nn.SequentialCell`两种方式。
# 
# ### Ops算子构建网络
# 
# [luojianet.ops]模块提供了基础算子的实现，如神经网络算子、数组算子和数学算子等。
# 
# 用户可使用`luojianet.ops`中的算子来构建一个简单的算法 $f(x)=x^2高级数据集管理+w$，示例如下：

# In[7]:


import numpy as np
import luojianet as ms
from luojianet import Parameter, Tensor, nn, ops

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.mul = ops.Mul()
        self.add = ops.Add()
        self.weight = Parameter(Tensor(np.array([2, 2, 2]), ms.float32))

    def forward(self, x):
        return self.add(self.mul(x, x), self.weight)

net = Net()
input = Tensor(np.array([1, 2, 3]), ms.float32)
output = net(input)

print(output)


# ### nn层构建网络
# 
# 尽管`luojianet.ops`模块提供的多样算子可以基本满足网络构建的诉求，但为了在复杂的深度网络中提供更方便易用的接口，[luojianet.nn]对`luojianet.ops`算子进行了进一步的封装。
# 
# `luojianet.nn`模块主要包括神经网络（neural network）中常用的卷积层（如`nn.Conv2d`）、池化层（`nn.MaxPool2d`）、非线性激活函数（如`nn.ReLU`）、损失函数（如`nn.LossBase`）、优化器（如`nn.Momentum`）等，为用户的使用提供了便利。
# 
# 下面示例代码中，使用`luojianet.nn`模块构建一个Conv + Batch Normalization + ReLu模型网络。

# In[8]:


import numpy as np
from luojianet import Tensor, nn

class ConvBNReLU(nn.Module):
    def __init__(self):
        super(ConvBNReLU, self).__init__()
        self.conv = nn.Conv2d(3, 64, 3)
        self.bn = nn.BatchNorm2d(64)
        self.relu = nn.ReLU()

    def forward(self, x):
        x = self.conv(x)
        x = self.bn(x)
        out = self.relu(x)
        return out

net = ConvBNReLU()
print(net)


# ### 容器构建网络
# 
# 为了便于管理和组成更复杂的网络，`luojianet.nn`提供了容器对网络中的子模型块或模型层进行管理，有`nn.ModuleList`和`nn.SequentialCell`两种方式。
# 
# 1中低阶API实现深度学习. CellList构建网络
# 
# 使用`nn.ModuleList`构造的Cell既可以是模型层，也可以是构建的网络子块。`nn.ModuleList`支持`append`、`extend`方法和`insert`方法三种方法。
# 
# 在运行网络时，可以在forward方法里，使用for循环，运行输出结果。
# 
# - `append(cell)`：在列表末尾添加一个cell。
# - `extend（cells)`：将cells添加至列表末尾。
# - `insert(index, cell)`：在列表给定的索引之前插入给定的cell。
# 
# 如下使用`nn.ModuleList`构建并执行一个网络，依次包含一个之前定义的模型子块ConvBNReLU、一个Conv2d层、一个BatchNorm2d层和一个ReLU层：

# In[9]:


import numpy as np
import luojianet
from luojianet import Tensor, nn

class MyNet(nn.Module):

    def __init__(self):
        super(MyNet, self).__init__()
        layers = [ConvBNReLU()]
        # 使用CellList对网络进行管理
        self.build_block = nn.CellList(layers)

        # 使用append方法添加Conv2d层和ReLU层
        self.build_block.append(nn.Conv2d(64, 4, 4))
        self.build_block.append(nn.ReLU())

        # 使用insert方法在Conv2d层和ReLU层中间插入BatchNorm2d
        self.build_block.insert(-1, nn.BatchNorm2d(4))

    def forward(self, x):
        # for循环执行网络
        for layer in self.build_block:
            x = layer(x)
        return x

net = MyNet()
print(net)


# 把数据输入到网络模型中：

# In[10]:


input = Tensor(np.ones([1, 3, 64, 32]), luojianet.float32)
output = net(input)
print(output.shape)


# 2高级数据集管理. SequentialCell构建网络
# 
# 使用`nn.SequentialCell`构造Cell顺序容器，支持子模块以List或OrderedDict格式作为输入。
# 
# 不同于`nn.ModuleList`的是，`nn.SequentialCell`类内部实现了`forward`方法，可以直接输出结果。
# 
# 如下示例使用`nn.SequentialCell`构建一个网络，输入为List，网络结构依次包含一个之前定义的模型子块ConvBNReLU、一个Conv2d层、一个BatchNorm2d层和一个ReLU层：

# In[11]:


import numpy as np
import luojianet
from luojianet import Tensor, nn

class MyNet(nn.Module):

    def __init__(self):
        super(MyNet, self).__init__()

        layers = [ConvBNReLU()]
        layers.extend([nn.Conv2d(64, 4, 4),
                       nn.BatchNorm2d(4),
                       nn.ReLU()])
        self.build_block = nn.SequentialCell(layers)  # 使用SequentialCell对网络进行管理

    def forward(self, x):
        return self.build_block(x)

net = MyNet()
print(net)


# 把数据输入到网络模型中：

# In[12]:


input = Tensor(np.ones([1, 3, 64, 32]), luojianet.float32)
output = net(input)
print(output.shape)


# 如下示例使用`nn.SequentialCell`构建一个网络，输入为OrderedDict：

# In[13]:


import numpy as np
import luojianet
from luojianet import Tensor, nn
from collections import OrderedDict

class MyNet(nn.Module):

    def __init__(self):
        super(MyNet, self).__init__()
        layers = OrderedDict()

        # 将cells加入字典
        layers["ConvBNReLU"] = ConvBNReLU()
        layers["conv"] = nn.Conv2d(64, 4, 4)
        layers["norm"] = nn.BatchNorm2d(4)
        layers["relu"] = nn.ReLU()

        # 使用SequentialCell对网络进行管理
        self.build_block = nn.SequentialCell(layers)

    def forward(self, x):
        return self.build_block(x)

net = MyNet()
print(net)

input = Tensor(np.ones([1, 3, 64, 32]), luojianet.float32)
output = net(input)
print(output.shape)


# ## nn与ops关系
# 
# `luojianet.nn`模块是Python实现的模型组件，对低阶API的封装，主要包括神经网络模型相关的各种模型层、损失函数、优化器等。
# 
# 同时`luojianet.nn`也提供了部分与`luojianet.ops`算子同名的接口，主要作用是对`luojianet.ops`算子进行进一步封装，为用户提供更友好的API。用户也可使用`luojianet.ops`算子根据实际场景实现自定义的网络。
# 
# 如下示例使用`luojianet.ops.Conv2D`算子实现卷积计算功能，即`nn.Conv2d`算子功能。

# In[14]:


import luojianet.nn as nn
import luojianet.ops as ops
from luojianet import Parameter
from luojianet.common.initializer import initializer


class Net(nn.Module):
    def __init__(self, in_channels=10, out_channels=20, kernel_size=3):
        super(Net, self).__init__()
        self.conv2d = ops.Conv2D(out_channels, kernel_size)
        self.bias_add = ops.BiasAdd()
        self.weight = Parameter(
            initializer('normal', [out_channels, in_channels, kernel_size, kernel_size]),
            name='conv.weight')
        self.bias = Parameter(initializer('normal', [out_channels]), name='conv.bias')

    def forward(self, x):
        """输入数据x"""
        output = self.conv2d(x, self.weight)
        output = self.bias_add(output, self.bias)
        return output

